<?php

declare (strict_types=1);

namespace think\admin\service;

use Error;
use ReflectionException;
use think\admin\Exception;
use think\admin\extend\CodeExtend;
use think\admin\Library;
use think\admin\model\SysMenu;
use think\admin\model\SysMerchant;
use think\admin\model\SysRelation;
use think\admin\model\SysUser;
use think\admin\Service;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\Session;

/**
 * 系统权限管理服务
 * Class AdminService
 * @package think\admin\service
 */
class AdminService extends Service
{
    /**
     * 自定义回调处理
     * @var array
     */
    private static $checkCallables = [];
    /**
     * 获取用户信息
     * @return array
     */
    public static function getUser(): array
    {
        if (static::isLogin()) {
            return Library::$sapp->session->get('user', []);
        } else {
            return [];
        }
    }

    /**
     * 是否已经登录
     * @return boolean
     */
    public static function isLogin(): bool
    {
        return static::getUserId() > 0;
    }

    /**
     * 获取后台用户ID
     * @return int
     */
    public static function getUserId(): int
    {
        return intval(Library::$sapp->session->get('user.id', 0));
    }

    /**
     * 获取商户ID
     * @return string
     */
    public static function getMerchantId(): string
    {
        if (static::getUserId() > 0) {
            return RelationService::getUserByMerchantId(static::getUserId());
        } else {
            return '-1';
        }
    }

    /**
     * 获取该用户所在商户信息
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public static function getMerchantList(): array
    {
        if (static::getUserId() > 0) {
            return RelationService::getUserByMerchantList(static::getUserId());
        } else {
            return [];
        }
    }

    /**
     * 是否为商户管理员
     * @return boolean
     */
    public static function isMerchantAdmin(): bool
    {
        return static::getAdminType() === '200';
    }

    /**
     * 获取用户类型值 超管100,商户管理员200
     * @return string
     */
    public static function getAdminType(): string
    {
        $userId = static::getUserId();
        $userType = static::userType();

        if ($userId > 0) {
            if ($userType === 'BIZ') {
                [$state, $data] = RelationService::isMerchant($userId);
                return $state ? '200' : '';
            } elseif ($userType === 'GLOBAL') {
                return '100';
            }
        }
        return '';
    }


    /**
     * 获取登录用户的类型“GLOBAL”,"BIZ"
     * @return string
     */
    public static function userType(): string
    {
        if (static::isLogin() && static::isSuper()) {
            return 'GLOBAL';
        } elseif (!static::isSuper()) {
            return RelationService::getUserByRoleType(static::getUserId());
        } else {
            return '';
        }
    }

    /**
     * 是否为超级用户
     * @return boolean
     */
    public static function isSuper(): bool
    {
        return static::getUserName() === static::getSuperName();
    }

    /**
     * 获取后台用户名称
     * @return string
     */
    public static function getUserName(): string
    {
        if (static::getUserId() > 0) {
            return Library::$sapp->session->get('user.account', '');
        } else {
            return '';
        }
    }

    /**
     * 获取超级用户账号
     * @return string
     */
    public static function getSuperName(): string
    {
        return Library::$sapp->config->get('app.super_user', 'superAdmin');
    }

    /**
     * 获取该用户菜单ID数组（角色关联菜单优先）
     * @return array
     */
    public static function getUserMenus(): array
    {
        $menus = [];
        if (!static::isSuper()) {
            $roleIds = static::getRoles();
            if (count(str2arr($roleIds)) > 0) {
                // 角色关联菜单
                $menus = SysRelation::getTargetIds('SYS_ROLE_HAS_RESOURCE', $roleIds);
            } else {
                // 用户关联菜单
                $menus = SysRelation::getTargetIds('SYS_USER_HAS_RESOURCE', (string)static::getUserId());
            }
        }
        // 查询拥有权限的菜单ID集合的上级ID数组
        $menuIds = SysMenu::getIds('id', implode(',', $menus), 'parent_id');
        // 得到上级ID集合
        $parentIds = array_unique($menuIds);
        // 合并上级ID与菜单ID
        return array_merge($parentIds, $menus);
    }


    /**
     * 获取该用户的角色集
     * @return string
     */
    public static function getRoles(): string
    {
        $userId = static::getUserId();
        if ($userId > 0) {
            // 用户关联角色，获取该用户所有角色id
            $roleIds = SysRelation::getTargetIds('SYS_USER_HAS_ROLE', (string)$userId);
            if (!$roleIds) {
                $roleIds = SysRelation::getObjectIds('SYS_ROLE_HAS_USER', (string)$userId);
            }
            return $roleIds ? implode(',', $roleIds) : '';
        }
        return '';
    }

    /**
     * 注册权限检查函数
     * @param callable $callable
     * @return integer
     */
    public static function registerCheckCallable(callable $callable): int
    {
        self::$checkCallables[] = $callable;
        return count(self::$checkCallables) - 1;
    }

    /**
     * 移除权限检查函数
     * @param ?integer $index
     * @return boolean
     */
    public static function removeCheckCallable(?int $index): bool
    {
        if (is_null($index)) {
            self::$checkCallables = [];
            return true;
        } elseif (isset(self::$checkCallables[$index])) {
            unset(self::$checkCallables[$index]);
            return true;
        } else {
            return false;
        }
    }

    /**
     * 检查指定节点授权
     * --- 需要读取缓存或扫描所有节点
     * @param null|string $node
     * @return boolean
     * @throws ReflectionException
     */
    public static function check(?string $node = ''): bool
    {
        $methods = NodeService::getMethods();
        $current = NodeService::fullNode($node);
        $userNodes = Library::$sapp->session->get('user.nodes', []);
        // 兼容 windows 控制器不区分大小写的验证问题
        foreach ($methods as $key => $rule) {
            if (preg_match('#.*?/.*?_.*?#', $key)) {
                $attr = explode('/', $key);
                $attr[1] = strtr($attr[1], ['_' => '']);
                $methods[join('/', $attr)] = $rule;
            }
        }
        // 自定义权限检查回调
        if (count(self::$checkCallables) > 0) {
            foreach (self::$checkCallables as $callable) {
                if ($callable($current, $methods, $userNodes) === false) {
                    return false;
                }
            }
            return true;
        }
        // 自定义权限检查方法
        if (function_exists('admin_check_filter')) {
            return call_user_func('admin_check_filter', $current, $methods, $userNodes);
        }

        // 超级用户权限
        if (static::isSuper()) {
            return true;
        }

        // 节点权限检查
        if (empty($methods[$current]['isauth'])) {
            return !(!empty($methods[$current]['islogin']) && !static::isLogin());
        } else {
            return in_array($current, $userNodes);
        }
    }

    /**
     * 初始化用户权限在用户登录时刷新权限
     * @param bool $force
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public static function apply(bool $force = false): array
    {
        if ($force) {
            static::clear();
        }
        $user = [];
        $userId = static::getUserId();
        if ($userId <= 0) {
            return $user;
        }

        $userData = SysUser::mk()
            ->where(['id' => $userId])
            ->field('account, avatar, created_at, email, id, name, phone, status, password, is_deleted')
            ->findOrEmpty();
        $userData->hidden(['sort', 'status', 'password', 'is_deleted']);
        $user = $userData->toArray();
        if (!$user['avatar']) {
            $user['avatar'] = '';
        }

        $user['nodes'] = [];
        $user['merchant'] = static::getMerchant();
        $roleCode = static::getRoleCode();
        $user['roleCode'] = $roleCode['code'];
        $user['roleName'] = $roleCode['name'];
        $user['authStatus'] = $user['merchant'] ? static::getMerchantAuthStatus($user['merchant']['self']) : 'NOT_AUTH';
        $user['infoType'] = $user['merchant'] ? static::getMerchantInfoType($user['merchant']['self']) : 'NORMAL';

        if (!static::isSuper()) {
            $resources = [];
            if (count(str2arr(static::getRoles())) > 0) {
                // 角色关联菜单
                $resources = SysRelation::mk()->where(['category' => 'SYS_ROLE_HAS_RESOURCE'])->whereIn('object_id', static::getRoles())->select()->toArray();
            } else {
                // 用户关联菜单
                $resources = SysRelation::mk()->where(['category' => 'SYS_USER_HAS_RESOURCE'])->whereIn('object_id', $userId)->toArray();
            }
            $menuIds = [];
            $buttonIds = [];
            foreach ($resources as $v) {
                $menuIds[] = $v['targetId'];
                $extJson = json_decode($v['extJson'], true);
                if (count($extJson['buttonInfo']) > 0) {
                    $buttonIds = array_merge($buttonIds, $extJson['buttonInfo']);
                }
            }
            $targetIds = array_merge($menuIds, $buttonIds);
            $ids = $targetIds ? implode(',', array_unique($targetIds)) : '';
            $user['nodes'] = $ids != '' ? SysMenu::mk()->whereIn('id', $ids)->column('code') : [];
            $user['nodes'] = array_unique($user['nodes']);
            sort($user['nodes']);
            if ($user['nodes']) {
                foreach ($user['nodes'] as &$v) {
                    // 驼峰转小写
                    $v = NodeService::fullNode($v);
                }
            }
        }

        Library::$sapp->session->set('user', $user);
        return $user;
    }

    /**
     * 清理节点缓存
     * @return bool
     */
    public static function clear(): bool
    {
        Library::$sapp->cache->delete('SystemAuthNode');
        return true;
    }

    /**
     * 获取该用户所在商户信息
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public static function getMerchant(): array
    {
        if (static::getUserId() > 0) {
            return RelationService::getUserByMerchant(static::getUserId());
        }
        return [];
    }


    /**
     * 获取用户账号类型
     * @return array
     */
    public static function getRoleCode(): array
    {
        $role = [];
        if (static::getUserId() > 0) {
            if (static::isSuper()) {
                $role = ['code' => 'superAdmin', 'name' => '平台超级管理员'];
            } else {
                $userType = static::getAdminType();
                switch ($userType) {
                    case '100':
                        $role = ['code' => 'globalAdmin', 'name' => '全局管理员'];
                        break;
                    case '200':
                        $role = ['code' => 'merchantAdmin', 'name' => '商户管理员'];
                        break;
                    default:
                        $role = ['code' => 'ordinaryAdmin', 'name' => '普通管理员'];
                }
            }
        }
        return $role;
    }


    /**
     * 获取商户认证信息
     * @param string $merchantId
     * @return string
     */
    public static function getMerchantAuthStatus(string $merchantId): string
    {
        if (!$merchantId) {
            return 'NOT_AUTH';
        }
        $auth = SysMerchant::mk()->where(['merchant_id' => $merchantId])->value('auth_status');
        return $auth ? $auth : 'NOT_AUTH';
    }

    /**
     * 获取商户信息规范
     * @param string $merchantId
     * @return string
     */
    public static function getMerchantInfoType(string $merchantId): string
    {
        if (!$merchantId) {
            return 'NORMAL';
        }
        $info = SysMerchant::mk()->where(['merchant_id' => $merchantId])->value('info_type');
        return $info ? $info : 'NORMAL';
    }

    /**
     * 获取会员上传配置
     * @param ?string $uptoken
     * @return array [unid,exts]
     */
    public static function withUploadUnid(?string $uptoken = null): array
    {
        try {
            if ($uptoken === '') return [0, []];
            $session = Library::$sapp->session;
            if (is_null($uptoken)) {
                $sesskey = $session->get('UploadSessionKey');
                if (empty($sesskey)) return [0, []];
                if ($session->getId() !== $sesskey) {
                    $session = Library::$sapp->invokeClass(Session::class);
                    $session->setId($sesskey);
                    $session->init();
                }
                $unid = intval($session->get('AdminUploadUnid', 0));
            } else {
                $sesskey = CodeExtend::decrypt($uptoken, sysconf('data.jwtkey'));
                if (empty($sesskey)) return [0, []];
                if ($session->getId() !== $sesskey) {
                    $session = Library::$sapp->invokeClass(Session::class);
                    $session->setId($sesskey);
                    $session->init();
                }
                if ($unid = intval($session->get('AdminUploadUnid', 0))) {
                    $session->set('UploadSessionKey', $session->getId());
                }
            }
            return [$unid, $session->get('AdminUploadExts', [])];
        } catch (\Error|\Exception $exception) {
            return [0, []];
        }
    }

    /**
     * 生成上传入口令牌
     * @param integer $unid 会员编号
     * @param string $exts 允许后缀(多个以英文逗号隔开)
     * @return string
     * @throws \think\admin\Exception
     */
    public static function withUploadToken(int $unid, string $exts = ''): string
    {
        Library::$sapp->session->set('AdminUploadUnid', $unid);
        Library::$sapp->session->set('AdminUploadExts', str2arr(strtolower($exts)));
        return CodeExtend::encrypt(Library::$sapp->session->getId(), sysconf('data.jwtkey'));
    }

    /**
     * 静态方法兼容(临时)
     * @param string $method
     * @param array $arguments
     * @return bool
     * @throws \think\admin\Exception
     */
    public static function __callStatic(string $method, array $arguments)
    {
        if (strtolower($method) === 'clearcache') return static::clear();
        throw new Exception("method not exists: AdminService::{$method}()");
    }

    /**
     * 对象方法兼容(临时)
     * @param string $method
     * @param array $arguments
     * @return bool
     * @throws \think\admin\Exception
     */
    public function __call(string $method, array $arguments)
    {
        return static::__callStatic($method, $arguments);
    }
}